﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FileLog
{
    public class FileLoggingProcessor : IDisposable
    {
        private readonly FileLogOptins _options;
        private readonly BlockingCollection<(string log, DateTimeOffset timestamp)>
          _messageQueue = new BlockingCollection<(string log, DateTimeOffset timestamp)>();
        //private readonly Queue<(string log, DateTimeOffset timestamp)>
        //  _messageQueue = new Queue<(string log, DateTimeOffset timestamp)>();
        private string? _logDir;
        private FileStream? _fileStream;
        private string? _logFileName;
        private readonly SyncTimer? _syncTimer;
        public FileLoggingProcessor(FileLogOptins options)
        {
            CreatDirectory(options);
            _options = options;
            _syncTimer = new SyncTimer(0, ProcessLogQueue, threadPriority: ThreadPriority.BelowNormal);
            _ = _syncTimer.StartAsync(default).ConfigureAwait(false);
        }
        /// <summary>
        /// 创建文件夹
        /// </summary>
        /// <param name="options"></param>
        /// <exception cref="InvalidOperationException"></exception>
        private void CreatDirectory(FileLogOptins options)
        {
            var dir = options.LogsDir ?? Environment.CurrentDirectory;
            var logPath = Path.Combine(dir, options.LogsDirectory);
            _logDir = logPath;
            if (!Directory.Exists(_logDir))
            {
                Directory.CreateDirectory(_logDir);
            }
        }


        public void EnqueueLog(string log, DateTimeOffset dateTimeOffset)
        {
            if (_messageQueue.IsAddingCompleted) return;
            _messageQueue.Add((log, dateTimeOffset));
            //_messageQueue.Enqueue((log, dateTimeOffset));
        }

        public void Dispose()
        {
            //_messageQueue.CompleteAdding();
            if (_messageQueue.Count > 0)//等待已有日志清空
            {
                Task.Delay(1000).ConfigureAwait(false).GetAwaiter().GetResult();
                Dispose();
            }
            _fileStream?.Flush();
            _fileStream?.Dispose();
             _messageQueue.Dispose();
            _syncTimer?.StopAsync(default).ConfigureAwait(false).GetAwaiter().GetResult();
        }

        private async Task ProcessLogQueue(CancellationToken cancellationToken)
        {
            try
            {
                if (_messageQueue.Count == 0)
                {
                    await Task.Delay(1000).ConfigureAwait(false);
                    return;
                }
                foreach (var message in _messageQueue.GetConsumingEnumerable(cancellationToken))
                {
                    await WriteLoggingEvent( message.log, message.timestamp, cancellationToken);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                throw new InvalidOperationException("日志写入文件错误", ex);
            }
        }

        private string GenerateLogFileName()
        {
            var rollingInterval = _options.RollingInterval;
            switch (rollingInterval)
            {
                case RollingInterval.Infinite: return _options.FileName;
                case RollingInterval.Year:
                    {
                        var yyyy = DateTime.Now.ToString("yyyy");
                        return FilePath(yyyy);
                    }
                case RollingInterval.Month:
                    {
                        var MM = DateTime.Now.ToString("yyyyMM");
                        return FilePath(MM);
                    };
                case RollingInterval.Day:
                    {
                        var dd = DateTime.Now.ToString("yyyyMMdd");
                        return FilePath(dd);
                    };
                case RollingInterval.Hour:
                    {
                        var dd = DateTime.Now.ToString("yyyyMMddHH");
                        return FilePath(dd);
                    };
                case RollingInterval.Minute:
                    {
                        var mm = DateTime.Now.ToString("yyyyMMddHHmm");
                        return FilePath(mm);
                    };
                default:
                    return _options.FileName;
            }
        }

        private string FilePath(string dd)
        {
            string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_options.FileName);
            string ext = Path.GetExtension(_options.FileName);
            return fileNameWithoutExtension + dd + ext;
        }
        private async Task<FileStream> CreateFileStream(string fileName)
        {
            var fullFileName = Path.Combine(_logDir, fileName);
            if (_fileStream == null)
            {
                _logFileName = fullFileName;
                _fileStream = new FileStream(fullFileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                return _fileStream;
            }
            if (fullFileName.Equals(_logFileName))
            {
                return _fileStream;
            }
            var fileStream = _fileStream;
            _logFileName = fullFileName;
            _fileStream = new FileStream(fullFileName, FileMode.Append, FileAccess.ReadWrite, FileShare.ReadWrite);
            await fileStream.FlushAsync();
            _fileStream.Close();
            _fileStream.Dispose();

            return _fileStream;

        }
        private async Task WriteLoggingEvent(string log, DateTimeOffset timestamp, CancellationToken cancellationToken)
        {
            var fileName = GenerateLogFileName();
            var fileStream = await CreateFileStream(fileName);
            //using  var writeStream = new StreamWriter(fileStream, encoding: Encoding.UTF8);
            var logstr = $"{log}";
             var by= Encoding.UTF8.GetBytes(logstr);
            await fileStream.WriteAsync(by);
            await fileStream.FlushAsync();
            //await writeStream.FlushAsync();
        }
    }
}
